#include "gtest/gtest.h"

#include "ObjectTest.h"
#include "helper/ScopedMemPtr.h"

using namespace helper;
using namespace object;
class ObjectEqualTest : public ObjectTest {
public:
    template<typename T, typename E, typename Creater>
    void testRawType(const E & expect, const E & expect2, const Creater & creater){
        ScopedMemPtr<T> raw = creater(expect);
        ASSERT_TRUE(expect == *raw);// value == RawType&
        ASSERT_FALSE(expect != *raw);// value != RawType&

        ASSERT_TRUE(*raw == expect); // RawType & == value
        ASSERT_FALSE(*raw != expect);// RawType & != value;

        ASSERT_TRUE(*raw == *raw); // RawType& == *this;
        ASSERT_FALSE(*raw != *raw); // RawType& != *this;

        ScopedMemPtr<T> rawSameValue = creater(expect);
        ASSERT_TRUE(*raw == *rawSameValue); // RawType& == RawType&
        ASSERT_FALSE(*raw != *rawSameValue);// RawType& != RawType&

        ScopedMemPtr<T> rawOther = creater(expect2);
        ASSERT_FALSE(*rawOther == *raw); //RawType & == RawType &
        ASSERT_TRUE(*rawOther != *raw);  //RawType & != RawType &
    }
};

TEST_F(ObjectEqualTest, testRawTypes){
    testRawType<Byte>((const char&)0x10, (const char&)0x20, [this](const char & expect) { 
        return this->getObjectFactory()->createByte(expect);
    });
    testRawType<Integer>((const long long&)0x10, (const long long&)0x20, [this](const long long & expect) { 
        return this->getObjectFactory()->createInteger(expect);
    });
    testRawType<Double>((const double&)0x10, (const double&)0x20, [this](const double & expect) { 
        return this->getObjectFactory()->createDouble(expect);
    });
    testRawType<String>((const std::string&)"hello", (const std::string&)"world", [this](const std::string & expect) {
        return this->getObjectFactory()->createString(expect);
    });
    testRawType<Pointer>((const void *)"o", (const void *)"d", [this](const void * expect) {
        return this->getObjectFactory()->createPointer(expect);
    });
}

TEST_F(ObjectEqualTest, testArrayEqualsDiffType){
    ScopedMemPtr<Array> left = getObjectFactory()->createArray();
    ScopedMemPtr<Hash> right = getObjectFactory()->createHash();
    ASSERT_FALSE(left->equals(*right));
    ASSERT_FALSE(right->equals(*left));
}

TEST_F(ObjectEqualTest, testArrayEqualsSameType){
    ScopedMemPtr<Array> left = getObjectFactory()->createArray();
    ASSERT_TRUE(left->equals(*left));

    ScopedMemPtr<Array> right = getObjectFactory()->createArray();
    ASSERT_TRUE(left->equals(*right));

    left->append('1');
    ASSERT_FALSE(left->equals(*right));
    right->append('1');
    ASSERT_TRUE(left->equals(*right));
    
    left->append(22ll);
    ASSERT_FALSE(left->equals(*right));
    right->append(22ll);
    ASSERT_TRUE(left->equals(*right));

    left->append(22.0);
    ASSERT_FALSE(left->equals(*right));
    right->append(22.0);
    ASSERT_TRUE(left->equals(*right));

    left->append("hello");
    ASSERT_FALSE(left->equals(*right));
    right->append("hello");
    ASSERT_TRUE(left->equals(*right));

    left->appendPointer("hello");
    ASSERT_FALSE(left->equals(*right));
    right->appendPointer("hello");
    ASSERT_TRUE(left->equals(*right));

    {
        Array * sub_array = getObjectFactory()->createArray(); 
        sub_array->append('2');
        left->append(sub_array);
    }
    ASSERT_FALSE(left->equals(*right));
    {
        Array * sub_array = getObjectFactory()->createArray(); 
        sub_array->append('2');
        right->append(sub_array);
    }
    ASSERT_TRUE(left->equals(*right));

    {
        Hash * sub_hash = getObjectFactory()->createHash();
        sub_hash->insert("char", (char)2);
        left->append(sub_hash);
    }
    ASSERT_FALSE(left->equals(*right));
    {
        Hash * sub_hash = getObjectFactory()->createHash();
        sub_hash->insert("char", (char)2);
        right->append(sub_hash);
    }
    ASSERT_TRUE(left->equals(*right));
}

TEST_F(ObjectEqualTest, testArrayUseOperation){
    ScopedMemPtr<Array> left = getObjectFactory()->createArray();
    ASSERT_EQ(*left, *left);

    ScopedMemPtr<Array> right = getObjectFactory()->createArray();
    ASSERT_EQ(*left, *right);

    left->append('1');
    ASSERT_NE(*left, *right);
    right->append('1');
    ASSERT_EQ(*left, *right);
    
    left->append(22ll);
    ASSERT_NE(*left, *right);
    right->append(22ll);
    ASSERT_EQ(*left, *right);

    left->append(22.0);
    ASSERT_NE(*left, *right);
    right->append(22.0);
    ASSERT_EQ(*left, *right);

    left->append("hello");
    ASSERT_NE(*left, *right);
    right->append("hello");
    ASSERT_EQ(*left, *right);

    left->appendPointer("hello");
    ASSERT_NE(*left, *right);
    right->appendPointer("hello");
    ASSERT_EQ(*left, *right);

    {
        Array * sub_array = getObjectFactory()->createArray(); 
        sub_array->append('2');
        left->append(sub_array);
    }
    ASSERT_NE(*left, *right);
    {
        Array * sub_array = getObjectFactory()->createArray(); 
        sub_array->append('2');
        right->append(sub_array);
    }
    ASSERT_EQ(*left, *right);

    {
        Hash * sub_hash = getObjectFactory()->createHash();
        sub_hash->insert("char", (char)2);
        left->append(sub_hash);
    }
    ASSERT_NE(*left, *right);
    {
        Hash * sub_hash = getObjectFactory()->createHash();
        sub_hash->insert("char", (char)2);
        right->append(sub_hash);
    }
    ASSERT_EQ(*left, *right);
}
TEST_F(ObjectEqualTest, testArrayNotEqual){
    auto * expect = getObjectFactory()->createArray();
    auto * array = getObjectFactory()->createArray();
    { // value1 != value2
        ASSERT_EQ(*expect, *array);
        ASSERT_EQ(0, expect->append("string"));
        ASSERT_EQ(0, array->append("string1"));
        ASSERT_NE(*expect, *array);
    }
    { // size(expect) != size(hash)
        expect->clear();
        array->clear();
        ASSERT_EQ(*expect, *array);

        ASSERT_EQ(0, expect->append("string"));
        ASSERT_NE(*expect, *array);
    }
}



TEST_F(ObjectEqualTest, testHashEquals){
    ScopedMemPtr<Hash> left = getObjectFactory()->createHash();
    ASSERT_TRUE(left->equals(*left));

    ScopedMemPtr<Hash> right = getObjectFactory()->createHash();
    ASSERT_TRUE(left->equals(*right));

    ASSERT_EQ(0, left->insert("byte", '1'));
    ASSERT_FALSE(left->equals(*right));
    ASSERT_EQ(0, right->insert("byte", '1'));
    ASSERT_TRUE(left->equals(*right));
    
    ASSERT_EQ(0, left->insert("integer", 22ll));
    ASSERT_FALSE(left->equals(*right));
    ASSERT_EQ(0, right->insert("integer", 22ll));
    ASSERT_TRUE(left->equals(*right));

    ASSERT_EQ(0, left->insert("double", 22.0));
    ASSERT_FALSE(left->equals(*right));
    ASSERT_EQ(0, right->insert("double", 22.0));
    ASSERT_TRUE(left->equals(*right));

    ASSERT_EQ(0, left->insert("string", "hello"));
    ASSERT_FALSE(left->equals(*right));
    ASSERT_EQ(0, right->insert("string", "hello"));
    ASSERT_TRUE(left->equals(*right));

    ASSERT_EQ(0, left->insertPointer("pointer", "hello"));
    ASSERT_FALSE(left->equals(*right));
    ASSERT_EQ(0, right->insertPointer("pointer", "hello"));
    ASSERT_EQ(*left, *right);

    {
        Array * sub_array = getObjectFactory()->createArray(); 
        ASSERT_EQ(0, sub_array->append('2'));
        ASSERT_EQ(0, left->insert("array", sub_array));
    }
    ASSERT_FALSE(left->equals(*right));
    {
        Array * sub_array = getObjectFactory()->createArray(); 
        sub_array->append('2');
        right->insert("array", sub_array);
    }
    ASSERT_TRUE(left->equals(*right));
    
    {
        Hash * sub_hash = getObjectFactory()->createHash();
        sub_hash->insert("char", (char)2);
        left->insert("hash", sub_hash);
    }
    ASSERT_FALSE(left->equals(*right));
    {
        Hash * sub_hash = getObjectFactory()->createHash();
        sub_hash->insert("char", (char)2);
        right->insert("hash", sub_hash);
    }
    ASSERT_TRUE(left->equals(*right));
}
TEST_F(ObjectEqualTest, testHashUseOperation){
    ScopedMemPtr<Hash> left = getObjectFactory()->createHash();
    ASSERT_EQ(*left, *left);

    ScopedMemPtr<Hash> right = getObjectFactory()->createHash();
    ASSERT_EQ(*left, *right);

    left->insert("char", '1');
    ASSERT_NE(*left, *right);
    right->insert("char", '1');
    ASSERT_EQ(*left, *right);
    
    left->insert("integer", 22ll);
    ASSERT_NE(*left, *right);
    right->insert("integer", 22ll);
    ASSERT_EQ(*left, *right);

    left->insert("double", 22.0);
    ASSERT_NE(*left, *right);
    right->insert("double", 22.0);
    ASSERT_EQ(*left, *right);

    left->insert("string", "hello");
    ASSERT_NE(*left, *right);
    right->insert("string", "hello");
    ASSERT_EQ(*left, *right);

    left->insertPointer("pointer", "hello");
    ASSERT_NE(*left, *right);
    right->insertPointer("pointer", "hello");
    ASSERT_EQ(*left, *right);

    {
        Array * sub_array = getObjectFactory()->createArray(); 
        sub_array->append('2');
        left->insert("array", sub_array);
    }
    ASSERT_NE(*left, *right);

    {
        Array * sub_array = getObjectFactory()->createArray(); 
        sub_array->append('2');
        right->insert("array", sub_array);
    }
    ASSERT_EQ(*left, *right);


    {
        Hash * sub_hash = getObjectFactory()->createHash();
        sub_hash->insert("char", (char)2);
        left->insert("hash", sub_hash);
    }
    ASSERT_NE(*left, *right);
    {
        Hash * sub_hash = getObjectFactory()->createHash();
        sub_hash->insert("char", (char)2);
        right->insert("hash", sub_hash);
    }
    ASSERT_EQ(*left, *right);
}
TEST_F(ObjectEqualTest, testHashNotEquals){
    auto * expect = getObjectFactory()->createHash();
    auto * hash = getObjectFactory()->createHash();
    {// value1 != value2
        ASSERT_EQ(0, expect->insert("key", "1"));
        ASSERT_EQ(0, hash->insert("key", "2"));
        ASSERT_NE(*expect, *hash);
    }
    { // size(expect) != size(hash)
        expect->clear();
        hash->clear();
        ASSERT_EQ(*expect, *hash);

        ASSERT_EQ(0, expect->insert("key", "1"));
        ASSERT_NE(*expect, *hash);
    }
    { // key1 != key2
        expect->clear();
        hash->clear();
        ASSERT_EQ(*expect, *hash);

        ASSERT_EQ(0, expect->insert("key1", "1"));
        ASSERT_EQ(0, hash->insert("key2", "1"));
        ASSERT_NE(*expect, *hash);
    }
}

TEST_F(ObjectEqualTest, testObject){
    ScopedMemPtr<Object> obj = getObjectFactory()->createByte('c');
    ScopedMemPtr<Object> other = getObjectFactory()->createByte('c');
    ASSERT_EQ(*obj, *other);
}
